
#include <QMap>
#include <QRegExp>
#include "futabaparser.h"
#include "text.h"
#include "components/postid.h"
#include "timestampparser.h"

namespace Wishmaster
{
	struct FutabaParserImpl
	{
		QString chanName;
		QString boardname;
		TimestampParser timestampParser;
	};

	FutabaParser::FutabaParser(const QString& chanName, const QString& boardname)
	{
		m_impl = new FutabaParserImpl;
		m_impl->chanName = chanName;
		m_impl->boardname = boardname;
	}

	FutabaParser::~FutabaParser()
	{
		delete m_impl;
	}

	QList<ThreadInfo> FutabaParser::getThreadList(const QString& chanName, const QString& boardId, const QByteArray& input)
	{
		QList<ThreadInfo> list;
		QString str(input);
		QString thread_regexp = "<hr>(.*)\\[<a href=\"(?:\\./)?res/(\\d+)(?:\\.html)?\">[^<]*</a>\\]</span>\\s+<blockquote>(.*)</blockquote>(?:<span class=\"omittedposts\">(\\d+))?.*<a";
		thread_regexp.replace(QString("%BOARD%"), m_impl->boardname);
		QRegExp rx(thread_regexp, Qt::CaseSensitive, QRegExp::RegExp2);
		rx.setMinimal(true);
		int pos = 0;
		unsigned int order = (1 << 30);
		while((pos = rx.indexIn(str, pos)) != -1)
		{
			ThreadInfo info;
			info.thread_id = ThreadId(chanName, boardId, rx.cap(2).toULong());
			info.topic = QString::fromUtf8(rx.cap(3).trimmed().toAscii());
			if(info.topic.isEmpty())
			{
				info.topic = QString("#") + info.thread_id.threadStringId();
			}
			else
			{
				info.topic.replace(QRegExp("<br[ /]*>"), " ");
				info.topic.remove(QRegExp("<[^>]*>"));
				info.topic = htmlToPlainText(info.topic);
			}
			int posts = rx.cap(4).toInt();
			if(posts == 0)
				info.posts = 1;
			else
				info.posts = posts + 4;
			info.timestamp = order--;
			QRegExp img("<img src=\"?([^\" ]*)\"? [^>]*></a><a");
			if(img.indexIn(rx.cap(1)) > 0)
				info.leaderPicUrl = QUrl(img.cap(1));
			pos += rx.matchedLength();
			list.append(info);
		}
		return list;
	}

	QList<PostInfo> FutabaParser::getPosts(const ThreadId& threadId, const QByteArray& input)
	{
		QList<PostInfo> list;
		QMap<PostId, int> postPositions;
		list.clear(); // just in case
		PostInfo oppost;
		if(!parseOpPost(oppost, input))
		{
			return list;
		}
		oppost.post_id = PostId(threadId.chanName(), threadId.boardId(), threadId.threadNumericId(), threadId.threadNumericId());
		list.append(oppost);

		postPositions.insert(oppost.post_id, 0);

		QString thread = QString::fromUtf8(input);
		QString regexp = QString::fromUtf8("<a name=\"(\\d+)\"></a>\\s+<table>.*<span class=\"replytitle\">([^<>]*)</span>\\s+<span class=\"commentpostername\">([^<>]*)</span>(.*)<span.*</span>(?:<br><a href=\"([^\"]*)\"[^>]*><img src=\"?(\\S+)\"? [^>]*></a>)?<blockquote>(.*)</blockquote>");
		QRegExp rx(regexp, Qt::CaseSensitive, QRegExp::RegExp2);
		rx.setMinimal(true);

		int pos = 0;
		int post_position = 1;
		while((pos = rx.indexIn(thread, pos)))
		{
			if(pos == -1)
				break;
			pos += rx.matchedLength();
			PostInfo post;
			post.topic = htmlToPlainText(rx.cap(2));
			post.text = rx.cap(7);
			post.name = htmlToPlainText(rx.cap(3));
			post.timestamp = m_impl->timestampParser.parse(rx.cap(4)).toTime_t();

			if(!rx.cap(5).isEmpty())
			{
				AttachmentInfo picture;
				picture.thumbUrl = QUrl(rx.cap(6));
				picture.url = QUrl(rx.cap(5));
				post.attachments.append(picture);
			}

			post.post_id = PostId(threadId.chanName(), threadId.boardId(), threadId.threadNumericId(), rx.cap(1).toULong());

			QList<PostId> links = findLinks(threadId, post.text);
			foreach(PostId link, links)
			{
				list[postPositions.value(link)].replies.insert(post.post_id);
			}

			list.append(post);
			postPositions.insert(post.post_id, post_position);
			post_position++;
		}
		return list;
	}

	QString FutabaParser::id()
	{
		return "futallaby";
	}

	bool FutabaParser::parseOpPost(PostInfo& info, const QByteArray& input)
	{
		QString thread = QString::fromUtf8(input);
		QString regexp = QString::fromUtf8("<a href=\"([^\"]*)\"[^>]*><img src=\"?(\\S+)\"? [^>]*></a><a name=\"(\\d+)\"></a>\\s+<input type=checkbox[^>]*><span class=\"filetitle\">([^<>]*)</span>\\s+<span class=\"postername\">([^<>]*)</span>(.*)<span.*<blockquote>(.*)</blockquote>");
		QRegExp rx(regexp, Qt::CaseSensitive, QRegExp::RegExp2);
		rx.setMinimal(true);

		int pos = 0;
		pos = rx.indexIn(thread, pos);
		if(pos == -1)
			return false;

		info.topic = htmlToPlainText(rx.cap(4));
		info.name = htmlToPlainText(rx.cap(5));
		info.timestamp = m_impl->timestampParser.parse(rx.cap(6)).toTime_t();
		info.text = rx.cap(7);

		AttachmentInfo picture;
		picture.thumbUrl = QUrl(rx.cap(2));
		picture.url = QUrl(rx.cap(1));
		info.attachments.append(picture);
		return true;
	}

	PostId FutabaParser::getPostID(const QString& link, const ThreadId& thread_id)
	{
		if(link.trimmed().isEmpty())
			return PostId();
		QString regexp = QString("(?:%1\\.html)?#(\\d+)").arg(thread_id.threadNumericId());
		QRegExp rx(regexp);
		if(rx.indexIn(link) != -1)
		{
			return PostId(thread_id.chanName(), thread_id.boardId(), thread_id.threadNumericId(), rx.cap(1).toULong());
		}
		rx.setPattern(QString("%1res/%2\\.html").arg(m_impl->boardname).arg(thread_id.threadNumericId()));
		if(rx.indexIn(link) != -1)
			return PostId(thread_id.chanName(), thread_id.boardId(), thread_id.threadNumericId(), thread_id.threadNumericId());
		return PostId();
	}

	QList<PostId> FutabaParser::findLinks(const ThreadId& threadId, const QString& posttext)
	{
		QList<PostId> links;
		QRegExp rx("<a href=\".*#(\\d+)\" class=\"quotelink\"");
		int pos = 0;
		while((pos = rx.indexIn(posttext, pos)) != -1)
		{
			unsigned int postId = rx.cap(1).toUInt();
			if(postId != 0)
			{
				links.append(PostId(threadId.chanName(), threadId.boardId(), threadId.threadNumericId(), postId));
			}
			pos += rx.matchedLength();
		}
		return links;
	}

	PostId FutabaParser::parseLink(const QString& url)
	{
		QRegExp rx("http://(.*)/(.*)/res/(\\d+)\\.html(?:#(\\d+))?$");
		if(rx.indexIn(url) < 0)
			return PostId();
		QString host = m_impl->chanName;
		return PostId(host, rx.cap(2).prepend('/').append('/'), rx.cap(3).toULong(), rx.cap(4).toULong());
	}

	BoardParser* FutabaParser::clone(const QString& chanName, const QString& boardname)
	{
		return new FutabaParser(chanName, boardname);
	}

	void FutabaParser::setTimestampFormat(const QString& format)
	{
		m_impl->timestampParser.setPattern(format);
	}
}

Q_EXPORT_PLUGIN2(futabaparser, Wishmaster::FutabaParser)
